package day32.Lambda;

import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

/**
 * Lambda表达式高级功能测试类
 * 
 * 本类演示了Lambda表达式的高级特性，包括自定义函数式接口、闭包、高级Stream操作、
 * 链式比较器、Optional结合使用、并行流以及复合函数等。
 * 
 * Lambda表达式是Java 8引入的重要特性，它允许我们将函数作为参数传递给另一个函数，
 * 或者将代码作为数据处理。Lambda表达式可以简化代码，使代码更加简洁易读。
 * 
 * 这些高级特性可以帮助我们编写更强大、更灵活的函数式代码。
 */
public class LambdaAdvancedTest {
    /**
     * 程序入口点
     * 
     * main方法是Java程序的执行入口点，程序从这里开始执行
     * 
     * @param args 命令行参数，是一个字符串数组，用于接收从命令行传入的参数
     *             在本程序中未使用该参数
     */
    public static void main(String[] args) {
        // 1. 自定义函数式接口
        // 函数式接口是只包含一个抽象方法的接口，可以使用Lambda表达式来实现
        System.out.println("=== 自定义函数式接口 ===");
        
        // 创建Calculator接口的实例，实现加法运算
        // Lambda表达式语法：(参数列表) -> {函数体}
        // 这里(a, b)是参数列表，a + b是函数体，表示将两个参数相加
        // Lambda表达式会自动推断参数类型，因此不需要显式声明参数类型
        Calculator add = (a, b) -> a + b;
        
        // 创建Calculator接口的实例，实现乘法运算
        // 同样使用Lambda表达式实现，这里执行的是乘法操作
        Calculator multiply = (a, b) -> a * b;
        
        // 调用calculate方法执行计算
        // add.calculate(5, 3)调用add实例的calculate方法，传入参数5和3
        // 结果是5+3=8
        System.out.println("5 + 3 = " + add.calculate(5, 3));
        
        // multiply.calculate(5, 3)调用multiply实例的calculate方法，传入参数5和3
        // 结果是5*3=15
        System.out.println("5 * 3 = " + multiply.calculate(5, 3));

        // 2. 闭包测试
        // 闭包是指函数可以访问并操作其外部作用域中的变量
        // 在Lambda表达式中，可以捕获外部作用域中的变量（必须是final或effectively final）
        System.out.println("\n=== 闭包测试 ===");
        
        // 创建Runnable列表用于演示闭包
        // Runnable是Java中的函数式接口，表示一个可以运行的任务
        List<Runnable> tasks = new ArrayList<>();
        
        // 在循环中创建Lambda表达式，捕获外部变量
        // for循环从0到2，创建3个任务
        for (int i = 0; i < 3; i++) {
            // effectively final变量：在Java 8中，即使没有显式声明为final，
            // 如果变量只被赋值一次，也被认为是final的
            final int index = i; // effectively final变量
            
            // Lambda表达式捕获外部的index变量，形成闭包
            // 闭包是指Lambda表达式可以访问其外部作用域中的变量
            // 每次循环都会创建一个新的Lambda表达式实例，捕获当前循环的index值
            tasks.add(() -> System.out.println("Task " + index + " executed"));
        }
        
        // 执行所有任务
        // forEach是Iterable接口的默认方法，接受一个Consumer函数式接口作为参数
        // Runnable::run是方法引用，等价于task -> task.run()
        tasks.forEach(Runnable::run);

        // 3. 高级Stream操作
        // Stream是Java 8引入的用于处理集合数据的API，提供了函数式编程的能力
        // 可以对集合进行过滤、映射、排序、聚合等操作
        System.out.println("\n=== 高级Stream操作 ===");
        
        // 创建Person对象列表用于演示高级Stream操作
        // Arrays.asList()方法创建一个固定大小的List，包含指定的元素
        List<Person> people = Arrays.asList(
            new Person("Alice", 25, "Female"),    // 创建一个名为Alice的Person对象，年龄25，性别Female
            new Person("Bob", 30, "Male"),        // 创建一个名为Bob的Person对象，年龄30，性别Male
            new Person("Charlie", 35, "Male"),    // 创建一个名为Charlie的Person对象，年龄35，性别Male
            new Person("Diana", 28, "Female"),    // 创建一个名为Diana的Person对象，年龄28，性别Female
            new Person("Eve", 22, "Female")       // 创建一个名为Eve的Person对象，年龄22，性别Female
        );

        // collect操作：将流转换为其他形式或执行副作用操作
        // Collectors.toList()收集器将流中的元素收集到List中
        // 这是一个链式操作：获取流 -> 过滤 -> 映射 -> 收集
        List<String> femaleNames = people.stream()                    // 获取people列表的Stream
            .filter(p -> "Female".equals(p.getGender()))              // 过滤女性，只保留性别为Female的Person对象
            .map(Person::getName)                                     // 映射操作，将Person对象转换为姓名字符串
            .collect(Collectors.toList());                            // 收集操作，将结果收集到List中
        System.out.println("女性名单: " + femaleNames);                // 输出女性名单

        // groupingBy操作：按指定条件对元素进行分组
        // Collectors.groupingBy()收集器根据分类函数对元素进行分组
        // 返回一个Map，键是分组条件的结果，值是该组的所有元素列表
        Map<String, List<Person>> groupByGender = people.stream()     // 获取people列表的Stream
            .collect(Collectors.groupingBy(Person::getGender));       // 按性别分组，键是性别，值是该性别的人列表
        System.out.println("按性别分组: " + groupByGender);            // 输出按性别分组的结果

        // partitioningBy操作：将元素分为两组（满足条件和不满足条件）
        // Collectors.partitioningBy()收集器根据谓词函数将元素分为两组
        // 返回一个Map，键是布尔值（true/false），值是满足/不满足条件的元素列表
        Map<Boolean, List<Person>> partitionByAge = people.stream()   // 获取people列表的Stream
            .collect(Collectors.partitioningBy(p -> p.getAge() >= 30)); // 按年龄分区，年龄>=30为true组，否则为false组
        System.out.println("按年龄分区 (>=30): " + partitionByAge);     // 输出按年龄分区的结果

        // summarizing操作：生成数值统计数据
        // Collectors.summarizingInt()收集器生成int值的统计数据
        // 返回IntSummaryStatistics对象，包含计数、总和、最小值、最大值、平均值等统计信息
        IntSummaryStatistics ageStats = people.stream()               // 获取people列表的Stream
            .collect(Collectors.summarizingInt(Person::getAge));      // 对年龄进行统计汇总
        System.out.println("年龄统计: " + ageStats);                   // 输出年龄统计信息

        // 4. 链式比较器
        // 链式比较器允许我们按照多个条件进行排序
        System.out.println("\n=== 链式比较器 ===");
        
        // 使用链式比较器进行多级排序
        // Comparator.comparing()创建一个比较器，根据提供的函数提取比较键
        // thenComparing()方法用于添加次要排序条件
        people.stream()                                               // 获取people列表的Stream
            .sorted(Comparator.comparing(Person::getAge)              // 首先按年龄排序（升序）
                    .thenComparing(Person::getName))                  // 年龄相同时按姓名排序（升序）
            .forEach(System.out::println);                            // 遍历并输出排序结果

        // 5. Optional与Lambda结合
        // Optional是Java 8引入的容器类，用于避免空指针异常
        // 可以将可能为null的值包装在Optional中，提供更安全的操作
        System.out.println("\n=== Optional与Lambda结合 ===");
        
        // 创建包含null和空字符串的列表用于演示Optional
        // Arrays.asList()创建一个包含指定元素的列表
        List<String> words = Arrays.asList("hello", null, "world", "", "java");
        
        // 使用Optional处理可能为null的值
        // 这是一个链式操作：获取流 -> 过滤 -> 映射 -> 过滤 -> 映射 -> 遍历
        words.stream()                                                // 获取words列表的Stream
            .filter(Objects::nonNull)                                 // 过滤掉null值，Objects::nonNull是方法引用，等价于obj -> obj != null
            .map(String::trim)                                        // 映射操作，去除字符串两端的空白字符
            .filter(s -> !s.isEmpty())                                // 过滤空字符串，保留非空字符串
            .map(String::toUpperCase)                                 // 映射操作，将字符串转换为大写
            .forEach(System.out::println);                            // 遍历并输出结果

        // 6. 并行流测试
        // 并行流可以利用多核处理器并行处理数据，提高处理速度
        System.out.println("\n=== 并行流测试 ===");
        
        // 创建大列表用于演示并行流
        // ArrayList是Java集合框架中的动态数组实现
        List<Integer> largeList = new ArrayList<>();
        
        // 使用for循环向列表中添加0到99的整数
        for (int i = 0; i < 100; i++) {
            largeList.add(i);                                         // 向列表中添加元素
        }
        
        // parallelStream()方法创建并行流，可以并行处理数据
        // 在多核处理器上可以提高处理速度
        long count = largeList.parallelStream()                       // 创建并行流
            .filter(n -> n % 2 == 0)                                  // 过滤偶数，保留能被2整除的数
            .count();                                                 // 计算元素个数
        System.out.println("偶数个数: " + count);                      // 输出偶数个数

        // 7. 复合函数
        // 复合函数允许我们将多个函数组合在一起形成新的函数
        System.out.println("\n=== 复合函数 ===");
        
        // 创建函数用于演示函数复合
        // Function是Java 8中的函数式接口，表示一个函数：接受一个参数，返回一个结果
        Function<Integer, Integer> multiplyByTwo = x -> x * 2;        // 创建一个函数，将输入乘以2
        Function<Integer, Integer> addTen = x -> x + 10;              // 创建一个函数，将输入加10
        
        // andThen方法：先执行调用者函数，再执行参数函数
        // 先乘以2再加10
        Function<Integer, Integer> multiplyThenAdd = multiplyByTwo.andThen(addTen);
        System.out.println("(5 * 2) + 10 = " + multiplyThenAdd.apply(5));
        
        // compose方法：先执行参数函数，再执行调用者函数
        // 先加10再乘以2
        Function<Integer, Integer> addThenMultiply = multiplyByTwo.compose(addTen);
        System.out.println("(5 + 10) * 2 = " + addThenMultiply.apply(5));
    }
}

/**
 * 自定义函数式接口
 * 
 * @FunctionalInterface注解用于标识这是一个函数式接口
 * 函数式接口是只包含一个抽象方法的接口
 * 可以使用Lambda表达式来实现函数式接口
 * 
 * 使用@FunctionalInterface注解的好处：
 * 1. 编译器会检查该接口是否确实只有一个抽象方法
 * 2. 提高代码可读性，明确表示这是一个函数式接口
 * 3. 如果添加了额外的抽象方法，编译器会报错
 */
@FunctionalInterface
interface Calculator {
    /**
     * 计算两个整数的结果
     * 
     * 这是Calculator函数式接口中唯一的抽象方法
     * 所有实现该接口的Lambda表达式都必须实现这个方法
     * 
     * @param a 第一个整数，参与计算的第一个操作数
     * @param b 第二个整数，参与计算的第二个操作数
     * @return 计算结果，返回两个整数经过某种运算后的结果
     */
    int calculate(int a, int b);
}

/**
 * Person类用于演示Lambda表达式的使用
 * 
 * 这是一个简单的POJO（Plain Old Java Object）类，包含姓名、年龄和性别三个属性
 * 用于演示Stream操作、比较器、分组等Lambda表达式的应用场景
 */
class Person {
    // Person类的属性
    private String name;      // 姓名，使用private修饰符封装数据
    private int age;          // 年龄，使用private修饰符封装数据
    private String gender;    // 性别，使用private修饰符封装数据

    /**
     * Person类的构造方法
     * 
     * 构造方法用于创建Person对象实例，并初始化对象的属性
     * 
     * @param name   姓名，用于初始化Person对象的name属性
     * @param age    年龄，用于初始化Person对象的age属性
     * @param gender 性别，用于初始化Person对象的gender属性
     */
    public Person(String name, int age, String gender) {
        // this关键字用于引用当前对象的属性，避免参数名与属性名冲突
        this.name = name;     // 将传入的name参数赋值给当前对象的name属性
        this.age = age;       // 将传入的age参数赋值给当前对象的age属性
        this.gender = gender; // 将传入的gender参数赋值给当前对象的gender属性
    }

    /**
     * 获取姓名
     * 
     * getter方法用于获取对象的属性值
     * 遵循JavaBean规范，方法名为get+属性名（首字母大写）
     * 
     * @return 姓名，返回Person对象的name属性值
     */
    public String getName() { 
        return name;          // 返回name属性的值
    }
    
    /**
     * 获取年龄
     * 
     * getter方法用于获取对象的属性值
     * 
     * @return 年龄，返回Person对象的age属性值
     */
    public int getAge() { 
        return age;           // 返回age属性的值
    }
    
    /**
     * 获取性别
     * 
     * getter方法用于获取对象的属性值
     * 
     * @return 性别，返回Person对象的gender属性值
     */
    public String getGender() { 
        return gender;        // 返回gender属性的值
    }

    /**
     * 重写toString方法，用于输出Person对象的信息
     * 
     * toString方法是Object类中的方法，每个Java对象都继承了该方法
     * 重写toString方法可以自定义对象的字符串表示形式，便于调试和输出
     * 
     * @return Person对象的字符串表示，包含姓名、年龄和性别信息
     */
    @Override
    public String toString() {
        // 返回Person对象的字符串表示形式
        // 包含类名、姓名、年龄和性别信息
        return "Person{name='" + name + "', age=" + age + ", gender='" + gender + "'}";
    }
}